#include "Timer.h"
#include <iostream>
#include <algorithm>
#include <functional>
#include <ctime>
#include <cassert>
using namespace std;

namespace CggUtil {

const int WORK_THREAD_CNT = 3;
const int TIMING_INTERV = 50;        // ms 
const int MAX_TASK_CNT  = 10000;     // decreases inaccuracy if exceeds

struct TaskCmp : public binary_function<Timer::Task, Timer::Task, bool> {
    bool operator() (const Timer::Task* t1, const Timer::Task* t2) const {
        return t1->activateTime > t2->activateTime;
    }
    bool operator() (const Timer::Task& t1, const Timer::Task& t2) const {
        return t1.activateTime > t2.activateTime;
    }
};

/* -----------------------------implementation----------------------------- */

Timer::Timer() :
m_stopTimer(false),
m_taskCnt(0) {
    
    for (int i = 0; i < WORK_THREAD_CNT; ++i) {
        auto t = new thread(&Timer::timingThreadProc, this);
        m_timingThreadTbl.insert(make_pair(t->get_id(), t));
    }

}

Timer::~Timer() {
    m_stopTimer = true;

    // wait until all work threads exit
    for (auto iter = m_timingThreadTbl.begin(); 
         iter != m_timingThreadTbl.end(); ++iter
    ) {
        auto t = iter->second;
        if (t->joinable()) {
            t->join();
        }
        delete t;
    }
}

bool Timer::addTask(Timer::Callback callback, int interval, void* params /*= NULL*/) {
    bool ret = true;
    assert(interval > 0);

    // limit task count
    if (m_taskCnt > MAX_TASK_CNT) {
        throw ExceedTaskLimitException(); 
    }

    m_newTaskLock.lock();

    try {
        Task* task = new Task;
        task->callback = callback;
        task->params = params;
        task->interval = interval;
        task->activateTime = clock() + task->interval - TIMING_INTERV;        
        m_newTaskQueue.push(task);
        ++m_taskCnt;
    } catch (const bad_alloc&) {
        ret = false;
    }

    m_newTaskLock.unlock();
    return ret;
}

void Timer::update() {
    Task* task = NULL;

    m_activeTaskTblLock.lock();

    while (!m_activeTaskQueue.empty()) {
        task = m_activeTaskQueue.front();
        m_activeTaskQueue.pop();

        // temporary release lock as callback may take a while to finish
        m_activeTaskTblLock.unlock();
        assert(task != NULL && task->callback != NULL);
        if (!task->callback(task->params, task->interval)) {
            delete task;
            task = NULL;
            --m_taskCnt;
        } else {
            m_newTaskLock.lock();
            assert(task->interval > 0);
            task->activateTime += task->interval;
            m_newTaskQueue.push(task);
            m_newTaskLock.unlock();
        }

        m_activeTaskTblLock.lock();
    } // end while

    m_activeTaskTblLock.unlock();
}

void Timer::timingThreadProc() {  
    auto taskTbl = new std::priority_queue<Task*, std::vector<Task*>, TaskCmp>; 

    while (!m_stopTimer) {
        clock_t clockStart = clock();
        Task* task = NULL;

        // insert new 
        m_newTaskLock.lock();
        while (!m_newTaskQueue.empty()) {
            task = m_newTaskQueue.front();
            m_newTaskQueue.pop();

            // release lock temporaryly, cause' it takes time inserting to a priority queue
            m_newTaskLock.unlock();
            taskTbl->push(task);
            m_newTaskLock.lock();
        }
        m_newTaskLock.unlock();

        // put timed over task into active table, erase finished task indicated by NULL callback
        while (!taskTbl->empty()) {
            task = taskTbl->top();
            assert(task->callback != NULL);

            if (task->activateTime <= clock()) {
                taskTbl->pop();
                m_activeTaskTblLock.lock();
                m_activeTaskQueue.push(task);
                m_activeTaskTblLock.unlock();
            } else {
                break;
            }
        }

        // sleep for next loop
        int elapsed = clock() - clockStart;
        if (elapsed < TIMING_INTERV) {
            this_thread::sleep_for(chrono::milliseconds(TIMING_INTERV - elapsed));
        }
    } // end of thread cycle

    // clean up
    delete taskTbl;
    taskTbl = NULL;
}


} // namespace CggUtil